package comm;


import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

import java.util.concurrent.LinkedBlockingQueue;

import comm.ClientSideMessage.Type;




public class ServerInterface implements ServerComm{
	public static final int PORTO = 8080;
	private ServerSocket serverSocket = null;
	private HashMap<String, SocketHandler> socketHandlersMap ;
	private LinkedBlockingQueue<ServerSideMessage> toServerMessageQueue;
	private boolean isOnline = false;
	public ServerInterface(){
		socketHandlersMap = new HashMap<String, SocketHandler>();
		toServerMessageQueue = new LinkedBlockingQueue<ServerSideMessage>();
	
	}
	
	@Override
	public void start() {
		
			try {
				serverSocket = new ServerSocket( ServerInterface.PORTO);	
				new SocketListener().run();
				this.isOnline= true;
			} catch (IOException e) {
				System.err.println("Could not listen on port: 8080 ");	
			}
			
	}

	@Override
	public ServerSideMessage getNextMessage() {
		
		return toServerMessageQueue.poll();
		
	}

	@Override
	public boolean hasNextMessage() {
		if(!toServerMessageQueue.isEmpty())
			return true;
		else 
			return false;
	}

	@Override
	public void sendMoodishMessage(String fromNickename, String toNickname, String moodishMessage) {
		
		ClientMessage newMoodishMessage = new ClientMessage( fromNickename, moodishMessage, Type.MOODISH_MESSAGE );
		sendMenssageToClient(newMoodishMessage , toNickname);
	}

	@Override
	public void sendNewFriendship(String toNickname, String newFriendship) {
		
		ClientMessage newFriendRequest = new ClientMessage( newFriendship , Type.FRIENDSHIP );
		sendMenssageToClient(newFriendRequest , toNickname);
	}

	@Override
	public void sendNewUnfriendship(String toNickname, String unfriendship) {
		
		ClientMessage removeFriendship = new ClientMessage(  unfriendship, Type.UNFRIENDSHIP );
		sendMenssageToClient(removeFriendship , toNickname);
	}

	@Override
	public void sendError(String toNickname, String error) {
		
		ClientMessage newErrorMessage = new ClientMessage( error, Type.ERROR );
		sendMenssageToClient(newErrorMessage , toNickname);
	}

	@Override
	public void sendClientConnected(String toNickname, String connectedNickname) {
		
		ClientMessage newConnectedRequest = new ClientMessage( connectedNickname, Type.CONNECTED );
		sendMenssageToClient(newConnectedRequest, toNickname);
	}

	@Override
	public void sendClientDisconnected(String toNickname, String disconnectedNickname) {
		
		ClientMessage newDisconnectedRequest = new ClientMessage( disconnectedNickname, Type.DISCONNECTED );
		sendMenssageToClient(newDisconnectedRequest , toNickname);
	}

	@Override
	public boolean clientIsConnected(String nickname) {
		
		if(socketHandlersMap.containsKey(nickname)){
			return true;
		}else{
			return false;
		}
	}

	@Override
	public void disconnectClient(String nickname) {
		socketHandlersMap.remove(nickname);
		
	}
	
	

	//este método serve para enviar as mensagens através do socket	
	public synchronized void sendMenssageToClient(ClientMessage message , String toNickname){
		socketHandlersMap.get(toNickname).sendMenssage(message);	
	}

	// Método que permite as threads adicionar mensagens à fila de mensagens, este método tem de ser sincronizado!!
	public synchronized void addMessageToList(ServerMessage message){
		toServerMessageQueue.add(message);
	}
	
	public synchronized void addHadlerToMap(SocketHandler handler, String key){
		socketHandlersMap.put(key, handler);
	}
	
	
	// ---------------------------- START inner class SocketListener --------------------------
	// inner class que extende a classe thread que vai ficar à ecuta de novas connecções, é instanciada quando se chama  
	//o método start()
	
	class SocketListener extends Thread{

	    public void run() {
	        System.out.println("Starting to listen for connections!");
	        
	        try {
	        	while(isOnline){
	        		Socket newConnection = serverSocket.accept();
	        		SocketHandler socketHandler = new SocketHandler(newConnection);
	        		
	        	}
			} catch (IOException e) {
				System.out.println("Closing server socket");
				e.printStackTrace();
			}
	        
	    }
	  
	}
//------------------------ END of SocketListener -------------------------
	
//------------------------ START inner class SocketHandler ---------------	
// estas threads vão ser adicionadas ao hashmap usando como key o nickname do utilizador a qual estão associadas.
// cada thread gere a sua ligação e fica à escuta de novas mensagens.
	
	class SocketHandler extends Thread{
		private Socket socket;
		private ObjectInputStream in;
		private ObjectOutputStream out;
		private Boolean connected = true;
		
		public SocketHandler(Socket s){
			this.socket = s;
			try {
				in = new ObjectInputStream(this.socket.getInputStream());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
		public void sendMenssage(ClientMessage message){
			try {
				out = new ObjectOutputStream(socket.getOutputStream());
				out.writeObject(message);
				out.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
		public void run(){
			  System.out.println("Starting to listen my client !");
			  while(connected){
				  ServerMessage inMessage;
				try {
					inMessage = (ServerMessage)in.readObject();
					if(inMessage.getType() == ServerSideMessage.Type.CLIENT_CONNECTED){
						addHadlerToMap(this, inMessage.getClientNickname()); 
					}else if(inMessage.getType() == ServerSideMessage.Type.CLIENT_DISCONNECTED){
						disconnectClient(inMessage.getClientNickname());
						connected = false;
					}else{
						addMessageToList(inMessage);
					}
				} catch (IOException e) {
					connected = false;
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				  	  
			  }
		}
	}
//----------------------END	of SocketHandlet ------------------------------
	
}
